using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Text;
using System.Xml;
using System.Reflection;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;

namespace EntLibContrib.Common.Configuration
{
    /// <summary>
    /// Defines a Polymorphic Configuration Element without having to be part of a PolymorphicConfigurationElementCollection.
    /// Works similar to what XmlSerializer does with xsi:type attribute.
    /// </summary>
    /// <remarks>The type attribute has to be the first one. If not specified, it is assumed the elements if of type T. 
    /// Implementation relies on private reflection as many methods are private on ConfigurationElement.
    /// </remarks>
    /// <typeparam name="T"></typeparam>
    public class TypeConfigurationElement<T> : AnonymousConfigurationElement
        where T : ConfigurationElement
    {
        private const string typeAttribute = "type";

        private AssemblyQualifiedTypeNameConverter typeConverter = new AssemblyQualifiedTypeNameConverter();

        private Type type;
        private T element;
        private bool isModified;

        /// <summary>
        /// Type provided by the Deserialization.
        /// </summary>
        public virtual Type Type
        {
            get { return type; }
        }

        /// <summary>
        /// The Element itself within the TypeConfigurationElement container.
        /// </summary>
        public T Element
        {
            get { return element; }
            set 
            { 
                element = value;
                
                isModified = true;
            }
        }

        /// <summary>
        /// The Element type.
        /// </summary>
        public virtual Type ElementType
        {
            get
            {
                if (element == null)
                {
                    return type;
                }
                else
                {
                    Type elementType = element.GetType();

                    return elementType == typeof(T) ? null : elementType;
                }
            }
        }

        /// <summary>
        /// Type used to create the Element during Deserialization.
        /// </summary>
        public virtual Type ElementTypeOrDefault
        {
            get { return ElementType == null ? typeof(T) : ElementType; }
        }

        protected override bool IsModified()
        {
            return isModified || base.IsModified();
        }

        /// <summary>
        /// Deserializes the Element from the content of the reader.
        /// </summary>
        /// <param name="reader"></param>
        public virtual void Deserialize(XmlReader reader)
        {
            type = GetType(reader);

            element = CreateElement();

            DoDeserializeElement(reader);
        }

        /// <summary>
        /// Creates a new instance of the Element.
        /// </summary>
        /// <returns>The newly created instance.</returns>
        protected virtual T CreateElement()
        {
            object instance = Activator.CreateInstance(ElementTypeOrDefault);
            
            return instance as T;
        }

        protected override void Unmerge(ConfigurationElement sourceElement, ConfigurationElement parentElement, ConfigurationSaveMode saveMode)
        {
            base.Unmerge(sourceElement, parentElement, saveMode);

            TypeConfigurationElement<T> castedSourceElement = sourceElement as TypeConfigurationElement<T>;

            Element = castedSourceElement.element;
        }

        protected virtual Type GetType(XmlReader reader)
        {
            Type type = null;

            if (reader.AttributeCount > 0)
            {
                reader.MoveToFirstAttribute();

                if (reader.Name == typeAttribute)
                {
                    type = GetType(reader.Value);
                }
            }

            if (type == null)
            {
                reader.MoveToElement();
            }

            return type;
        }

        protected virtual Type GetType(string typeName)
        {
            return (Type)typeConverter.ConvertFromString(typeName);
        }

        protected virtual string ElementTypeName
        {
            get { return ElementType == null ? null : typeConverter.ConvertToString(ElementType); }
        }

        protected override bool SerializeElement(XmlWriter writer, bool serializeCollectionKey)
        {
            if (writer != null)
            {
                if (!string.IsNullOrEmpty(ElementTypeName))
                {
                    writer.WriteAttributeString(typeAttribute, ElementTypeName);
                }

                DoSerializeElement(writer);
            }

            return true;
        }

        protected virtual void DoSerializeElement(XmlWriter writer)
        {
            object[] parameters = { writer, false };

            MethodInfo serializeElementMethod = typeof(ConfigurationElement).GetMethod("SerializeElement", BindingFlags.Instance | BindingFlags.NonPublic);

            serializeElementMethod.Invoke(Element, parameters);
        }

        protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey)
        {
            Deserialize(reader);
        }

        protected virtual void DoDeserializeElement(XmlReader reader)
        {
            object[] parameters = { reader, false };

            MethodInfo deserializeElementMethod = typeof(ConfigurationElement).GetMethod("DeserializeElement", BindingFlags.Instance | BindingFlags.NonPublic);

            deserializeElementMethod.Invoke(Element, parameters);
        }
    }
}
